package com.hzqdn.monsteruser.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hzqdn.monstercommon.common.ErrorCode;
import com.hzqdn.monstercommon.constant.CommonConstant;
import com.hzqdn.monstercommon.constant.RedisConstant;
import com.hzqdn.monstercommon.constant.UserConstant;
import com.hzqdn.monstercommon.exception.BusinessException;
import com.hzqdn.monstercommon.utils.SqlUtils;
import com.hzqdn.monstermodel.dto.user.UserQueryRequest;
import com.hzqdn.monstermodel.entity.User;
import com.hzqdn.monstermodel.enums.AvatarEnum;
import com.hzqdn.monstermodel.enums.UserRoleEnum;
import com.hzqdn.monstermodel.vo.LoginUserVO;
import com.hzqdn.monstermodel.vo.UserVO;
import com.hzqdn.monsteruser.mapper.UserMapper;
import com.hzqdn.monsteruser.satoken.DeviceUtils;
import com.hzqdn.monsteruser.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBitSet;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 用户服务实现
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private RedisTemplate redisTemplate;

    public static final String SALT = "hzqdn";

    @Override
    public long userRegister(String userAccount, String userPassword, String checkPassword) {
        // 参数校验
        if (userAccount.length() < 4 || userAccount.length() > 16) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号不符合要求!");
        }
        if (userPassword.length() < 8 || userPassword.length() > 16) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码不符合要求!");
        }
        if (!userPassword.equals(checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致!");
        }
        // 数据库设置userAccount字段唯一索引，保证账号不重复
        // 判断账号重复
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        if ( this.getOne(queryWrapper)  != null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号已存在");
        }
        // 密码加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 插入数据
        User user = new User();
        user.setUserName(userAccount);
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        user.setUserRole(UserRoleEnum.USER.getValue());
        user.setUserAvatar(AvatarEnum.getRandomAvatar());
        user.setExpertiseDirection("暂无");
        if (!this.save(user)) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败");
        }
        return user.getId();
    }

    @Override
    public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 查询用户是否存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 只查询必要字段
        queryWrapper.select("id", "userName", "userAccount", "userRole")
                .eq("userAccount", userAccount)
                .eq("userPassword", encryptPassword);
        User user = this.getOne(queryWrapper);
        // 用户不存在
        if (ObjectUtils.isEmpty(user)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
        }
        // session 记录
        request.setAttribute(UserConstant.USER_LOGIN_STATE, user);
        // 使用 Sa-Token 记录登录状态
        StpUtil.login(user.getId(), DeviceUtils.getRequestDevice(request));
        StpUtil.getSession().set(UserConstant.USER_LOGIN_STATE, user);
        return this.getLoginUserVO(user);
    }

    @Override
    public LoginUserVO getLoginUserVO(User user) {
        if (ObjectUtils.isEmpty(user)) {
            return null;
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        BeanUtils.copyProperties(user, loginUserVO);
        return loginUserVO;
    }

    @Override
    public Boolean userLogout(HttpServletRequest request) {
        // 检查登录状态
        StpUtil.checkLogin();
        // 移除登录态
        StpUtil.logout();
        // 移除 session 中的用户信息
        request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE);
        return true;
    }

    @Override
    public User getLoginUser(HttpServletRequest request) {
        Object loginUserId = StpUtil.getLoginIdDefaultNull();
        if (ObjectUtils.isEmpty(loginUserId)) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        User user = this.getById((Long)loginUserId);
        if (user == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在");
        }
        return user;
    }

    /**
     * 获取脱敏的用户信息
     */
    @Override
    public UserVO getUserVO(User user) {
        if (user == null) {
            return null;
        }
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        return userVO;
    }

    @Override
    public QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest) {
        if (userQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }
        Long id = userQueryRequest.getId();
        String unionId = userQueryRequest.getUnionId();
        String mpOpenId = userQueryRequest.getMpOpenId();
        String userName = userQueryRequest.getUserName();
        String userProfile = userQueryRequest.getUserProfile();
        String userRole = userQueryRequest.getUserRole();
        String sortField = userQueryRequest.getSortField();
        String sortOrder = userQueryRequest.getSortOrder();

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(id != null, "id", id)
                .eq(StringUtils.isNotBlank(unionId), "unionId", unionId)
                .eq(StringUtils.isNotBlank(mpOpenId), "mpOpenId", mpOpenId)
                .eq(StringUtils.isNotBlank(userRole), "userRole", userRole)
                .like(StringUtils.isNotBlank(userProfile), "userProfile", userProfile)
                .like(StringUtils.isNotBlank(userName), "userName", userName)
                .orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), sortField);
        return queryWrapper;
    }

    @Override
    public List<UserVO> getUserVO(List<User> userList) {
        if (CollUtil.isEmpty(userList)) {
            return new ArrayList<>();
        }
        return userList.stream()
                .map(this::getUserVO)
                .collect(Collectors.toList());
    }

    @Override
    public boolean addUserSignIn(Long userId) {
        LocalDate date = LocalDate.now();
        String key = RedisConstant.getUserSignInRedisKey(date.getYear(), userId);
        // 获取 Redis 的 BitMap
        RBitSet signInBitSet = redissonClient.getBitSet(key);
        // 获取当前日期是一年中的第几天，作为偏移量（从 1 开始计数）
        int offset = date.getDayOfYear();
        // 查询当天有没有签到
        if (!signInBitSet.get(offset)) {
            // 如果当前未签到，则设置
            signInBitSet.set(offset, true);
        }
        // 当天已签到
        return true;
    }

    @Override
    public List<Integer> getUserSignInRecord(Long userId, Integer year) {
        if (year == null) {
            LocalDate date = LocalDate.now();
            year = date.getYear();
        }
        String key = RedisConstant.getUserSignInRedisKey(year, userId);
        // 获取 Redis 的 BitMap
        RBitSet signInBitSet = redissonClient.getBitSet(key);
        // 加载 BitSet 到内存中，避免后续读取时发送多次请求
        BitSet bitSet = signInBitSet.asBitSet();
        // 统计签到的日期
        List<Integer> dayList = new ArrayList<>();
        // 从索引 0 开始查找下一个被设置为 1 的位
        int index = bitSet.nextSetBit(0);
        while (index >= 0) {
            dayList.add(index);
            // 继续查找下一个被设置为 1 的位
            index = bitSet.nextSetBit(index + 1);
        }
        return dayList;
    }

    @Override
    public List<UserVO> getUserSignInRank(Integer limit, Integer year, Integer month) {
        if (limit == null) {
            limit = 10;
        }
        String rankKey;
        if (year == null) {
            // 总排行榜
            rankKey = RedisConstant.getUserSignInTotalRankKey();
        } else if (month == null) {
            // 年度排行榜
            rankKey = RedisConstant.getUserSignInRankKey(year);
        } else {
            // 月度排行榜
            rankKey = RedisConstant.getUserSignInMonthlyRankKey(year, month);
        }
        String userKey = RedisConstant.getUserRankCacheKey();

        // 1. 先从Redis获取排行榜
        Set<Object> rankSet = redisTemplate.opsForZSet().reverseRange(rankKey, 0, limit - 1);

        if (rankSet != null && !rankSet.isEmpty()) {
            // Redis中存在数据，直接返回
            return rankSet.stream()
                    .map(id -> {
                        // 先从Redis获取用户信息
                        UserVO userVO = (UserVO) redisTemplate.opsForHash().get(userKey, String.valueOf(id));
                        if (userVO == null) {
                            // Redis中没有，从数据库获取并缓存
                            User user = this.getById((Serializable) id);
                            userVO = getUserVO(user);
                            redisTemplate.opsForHash().put(userKey, String.valueOf(id), userVO);
                        }
                        // 设置签到数
                        Double score = redisTemplate.opsForZSet().score(rankKey, id);
                        userVO.setSignInCount(score != null ? score.intValue() : 0);
                        return userVO;
                    })
                    .collect(Collectors.toList());
        }

        // 2. Redis中没有数据，从数据库查询并写入Redis
        List<UserVO> rankList = this.baseMapper.selectList(new QueryWrapper<>())
                .stream()
                .map(user -> {
                    UserVO userVO = getUserVO(user);
                    int signInCount;

                    if (year == null) {
                        // 统计所有年份的签到数
                        signInCount = getAllYearsSignInCount(user.getId());
                    } else if (month == null) {
                        // 统计指定年份的签到数
                        List<Integer> signInDays = getUserSignInRecord(user.getId(), year);
                        signInCount = signInDays.size();
                    } else {
                        // 统计指定月份的签到数
                        List<Integer> signInDays = getUserSignInRecord(user.getId(), year);
                        signInCount = (int) signInDays.stream()
                                .filter(day -> {
                                    LocalDate date = LocalDate.ofYearDay(year, day);
                                    return date.getMonthValue() == month;
                                })
                                .count();
                    }

                    userVO.setSignInCount(signInCount);
                    // 写入排行榜数据
                    redisTemplate.opsForZSet().add(rankKey, user.getId(), signInCount);
                    // 写入用户信息缓存
                    redisTemplate.opsForHash().put(userKey, String.valueOf(user.getId()), userVO);
                    return userVO;
                })
                .sorted((a, b) -> b.getSignInCount() - a.getSignInCount())
                .limit(limit)
                .collect(Collectors.toList());

        return rankList;
    }

    @Override
    public Boolean isAdmin() {
        Object userObj = StpUtil.getSession().get(UserConstant.USER_LOGIN_STATE);
        User user = (User) userObj;
        return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole());
    }

    private int getAllYearsSignInCount(Long userId) {
        int currentYear = LocalDate.now().getYear();
        int totalCount = 0;
        // 统计从2023年（或系统上线年份）到当前年份的所有签到数
        for (int year = 2023; year <= currentYear; year++) {
            List<Integer> signInDays = getUserSignInRecord(userId, year);
            totalCount += signInDays.size();
        }
        return totalCount;
    }

}






